package com.gui;

import com.gooddies.wiring.Wiring;
import com.gooddies.wiring.annotations.PostWiring;
import com.gooddies.wiring.annotations.Wire;
import com.gooddies.wiring.annotations.WiringComponent;
import com.gui.interfaces.ErrorListContainer;
import com.gui.interfaces.StatusTextManipulator;
import com.gui.textFields.AbstractTextField;
import com.peg.ParserConstructor;
import com.peg.PegRuleWalker;
import com.peg.dataobjects.BoolValue;
import com.peg.dataobjects.ErrorMessage;
import com.peg.rules.Rule;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Highlighter;

/**
 * @author sad
 */
@WiringComponent(singleton = true)
public class GrammarEditor extends AbstractTextField {

    private RepeatableTimer timer;
    private final List<Object> highlighters = new ArrayList<>();
    private List<ErrorMessage> errors = new ArrayList<>();
    @Wire("MainFrame")
    private StatusTextManipulator statusTextContainer;
    @Wire("MainFrame")
    private ErrorListContainer errorContainer;
    private PegHighlighter grammarHighlihter;
    private ParserConstructor constructor;
    @Wire
    private ParsingContext parsingContext;
    @Wire("GrammarLineNumbers")
    private TextLineNumber textLineNumber;
    @Wire
    private Breakpoints breakpoints;

    public GrammarEditor() {
    }

    @PostWiring
    private void init() {
        grammarHighlihter = (PegHighlighter) Wiring.get().getWiringComponent(PegHighlighter.class);
        setToolTipText("tooltip");
        timer = new RepeatableTimer(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    parse();
                } catch (BadLocationException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }, 500);
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                timer.restart();
            }
        });
        addCaretListener(new CaretListener() {
            @Override
            public void caretUpdate(CaretEvent e) {
                caretChangedPosition(e.getDot());
            }
        });
        textLineNumber.addLineNumberClickEvent(new TextLineNumber.LineNumberClick() {

            @Override
            public void lineClick(int lineNumber) {
                addBreakPoint(lineNumber);
            }
        });
    }

    private void addBreakPoint(int line) {
        if (breakpoints.hasBreakpoint(line)) {
            String tag = breakpoints.getTag(line);
            removeHighlight(tag);
            breakpoints.removeBreakpoint(line);
        } else {
            String tag = breakpoints.addBreakpoint(line);
            highlightLine(tag, line, Color.RED);
        }
    }

    private void caretChangedPosition(final int pos) {
        if (constructor != null) {
            final BoolValue value = new BoolValue();
            constructor.ruleWalker(new PegRuleWalker() {
                @Override
                public void ruleWalk(Rule rule) {
                    if (rule.getStart() <= pos && (rule.getStart() + rule.getLength()) >= pos) {
                        statusTextContainer.setStatusText(rule.toString());
                        value.setValue(true);
                    }
                }
            });

            if (!value.isValue()) {
                statusTextContainer.setStatusText("");
            }
        }
    }

    private void parse() throws BadLocationException {
        String grammar = getDocument().getText(0, getDocument().getLength());
        constructor = new ParserConstructor(grammar);
        parsingContext.setBaseParser(constructor.createParser());
        boolean errorWas = (errors != null && !errors.isEmpty());
        clearHighlight();
        errorContainer.clearErrors();
        grammarHighlihter.process(this, constructor);
        errors = constructor.getErrors();
        if (!constructor.getErrors().isEmpty()) {
            for (ErrorMessage err : constructor.getErrors()) {
                errorContainer.addError(err);
                addErrorHightlight(err.getStartIndex(), err.getLength());
            }
        }

        boolean errorIs = (errors != null && !errors.isEmpty());
        if (errorIs != errorWas) {
            invalidate();
            repaint();
        }
    }

    @Override
    public String getToolTipText(MouseEvent event) {
        int caretPosition = viewToModel(event.getPoint());
        for (ErrorMessage error : errors) {
            int start = error.getStartIndex();
            int end = error.getLength() + start;
            if (start <= caretPosition && end >= caretPosition) {
                return error.getMessage();
            }
        }
        return null;
    }

    public void clearHighlight() {
        for (Object h : highlighters) {
            getHighlighter().removeHighlight(h);
        }

        highlighters.clear();
    }

    public void addHightlight(int index, int length, Highlighter.HighlightPainter painter) {
        try {
            Object hightlighter = getHighlighter().addHighlight(index, index + length, painter);
            highlighters.add(hightlighter);
        } catch (BadLocationException ex) {
            throw new RuntimeException(ex);
        }
    }

    public void addErrorHightlight(int index, int length) {
        Highlighter.HighlightPainter painter = (Highlighter.HighlightPainter) Wiring.get().getWiringComponent("ErrorHighlighter");
        try {
            Object hightlighter = getHighlighter().addHighlight(index, index + length, painter);
            highlighters.add(hightlighter);
        } catch (BadLocationException ex) {
            throw new RuntimeException(ex);
        }
    }
}
